/*	
*	File: arp-header.h
*	Date: 2011/11/25
*	User: Ronmaru
*	Desctiption: arp header class
*/

#ifndef ARP_HEADER_H
#define ARP_HEADER_H

#include <istream>
#include <ostream>
#include <algorithm>
#include "mac-address-type.h"
#include <boost/asio/ip/address_v4.hpp>

// ARP header
//
// The wire format of an ARP header is:
// 
// 0               8               16                             31
// +-------------------------------+------------------------------+      ---
// |                               |                              |       ^
// |     Hardware type (HTYPE)     |    Protocol type (PTYPE)     |       |
// |                               |                              |       |
// +---------------+---------------+------------------------------+    4 bytes
// |               |               |                              |       ^
// |   Hard. len.  |  Proto. len.  |       Operation (OPER)       |       |
// |    (HLEN)     |    (PLEN)     |                              |       |
// +-------------------------------+------------------------------+    8 bytes
// |                                                              |       ^
// |                  Sender hardware address (SHA)               |       |
// |                                                              |       |
// +--------------------------------------------------------------+    14 bytes
// |                                                              |       ^
// |                  Sender protocol address (SPA)               |       |
// |                                                              |       |
// +--------------------------------------------------------------+    18 bytes
// |                                                              |       ^
// |                  Target hardware address (THA)               |       |
// |                                                              |       |
// +--------------------------------------------------------------+    24 bytes
// |                                                              |       ^
// |                  Target protocol address (TPA)               |       |
// |                                                              |       |
// +--------------------------------------------------------------+    28 bytes


const int ARP_MAX_HEADER_LENGTH = 28;

class ARPheader
{
public:
  ARPheader() { std::fill(rep_, rep_ + sizeof(rep_), 0); }

  void SetHTYPE(unsigned short n) { Encode(0, 1, n); }
  void SetPTYPE(unsigned short n) { Encode(2, 3, n); }
  void SetHLEN(unsigned char n) { rep_[4] = n; }
  void SetPLEN(unsigned char n) { rep_[5] = n; }
  void SetOPER(unsigned short n) { Encode(6, 7, n); }
  void SetSHA(const MacAddress &mac_address) { 
    for (int i = 0; i < mac_address.size(); ++i) {
      rep_[8 + i] = mac_address[i];
    }
  }
  void SetSPA(boost::asio::ip::address_v4 address) { 
    boost::asio::ip::address_v4::bytes_type bytes = address.to_bytes();
    rep_[14] = bytes[0];
    rep_[15] = bytes[1];
    rep_[16] = bytes[2];
    rep_[17] = bytes[3];
  }
  void SetTHA(const MacAddress &mac_address) { 
    for (int i = 0; i < mac_address.size(); ++i) {
      rep_[18 + i] = mac_address[i];
    }
  }
  void SetTPA(boost::asio::ip::address_v4 address) { 
    boost::asio::ip::address_v4::bytes_type bytes = address.to_bytes();
    rep_[24] = bytes[0];
    rep_[25] = bytes[1];
    rep_[26] = bytes[2];
    rep_[27] = bytes[3];
  } 

  unsigned short GetHTYPE() const { return Decode(0, 1); }
  unsigned short GetPTYPE() const { return Decode(2, 3); }
  unsigned char GetHLEN() const { return rep_[4]; }
  unsigned char GetPLEN() const { return rep_[5]; }
  unsigned short GetOPER() const { return Decode(6, 7); }
  MacAddress GetSHA() const { 
    MacAddress mac_address;
    for (int i = 0; i < 6; ++i) {
      mac_address.push_back(rep_[8 + i]);
    }
    return mac_address;
  }
  boost::asio::ip::address_v4 GetSPA() const {
    boost::asio::ip::address_v4::bytes_type bytes
      = { { rep_[14], rep_[15], rep_[16], rep_[17] } };
    return boost::asio::ip::address_v4(bytes);
  }
  MacAddress GetTHA() const { 
    MacAddress mac_address;
    for (int i = 0; i < 6; ++i) {
      mac_address.push_back(rep_[18 + i]);
    }
    return mac_address;
  }
  boost::asio::ip::address_v4 GetTPA() const {
    boost::asio::ip::address_v4::bytes_type bytes
      = { { rep_[24], rep_[25], rep_[26], rep_[27] } };
    return boost::asio::ip::address_v4(bytes);
  }

  friend std::istream& operator>>(std::istream& is, ARPheader& header)
  { return is.read(reinterpret_cast<char*>(header.rep_), ARP_MAX_HEADER_LENGTH); }

  friend std::ostream& operator<<(std::ostream& os, const ARPheader& header)
  { return os.write(reinterpret_cast<const char*>(header.rep_), ARP_MAX_HEADER_LENGTH); }

private:
  unsigned short Decode(int a, int b) const
  { return (rep_[a] << 8) + rep_[b]; }

  void Encode(int a, int b, unsigned short n)
  {
    rep_[a] = static_cast<unsigned char>(n >> 8);
    rep_[b] = static_cast<unsigned char>(n & 0xFF);
  }

  unsigned char rep_[ARP_MAX_HEADER_LENGTH];
};

#endif // ARP_HEADER_H